﻿/*************************************************************************************
 * 文 件 名:   HK_Foldup
 * 
 * 描    述:   Inspector序列化字段自动折叠特性
 * 
 * 版    本：  V1.0
 * 创 建 者：  京产肠饭
 * 创建时间：  2022/12/19
 * 链   接：  https://gitee.com/JingChanChangFan/hk_-unity-tools
*************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;

namespace HKTools
{
    [CustomEditor(typeof(MonoBehaviour), true, isFallback = true)]
    [CanEditMultipleObjects]
    public class HK_Foldup : Editor
    {
        Dictionary<string, CacheFoldProp> cacheFoldsDic = new Dictionary<string, CacheFoldProp>();
        List<SerializedProperty> propsList = new List<SerializedProperty>();

        bool isInit;

        void OnEnable()
        {
            isInit = false;
        }

        void OnDisable()
        {
            if (target == null)
                return;

            foreach (var keyValuePair in cacheFoldsDic)
            {
                keyValuePair.Value.Dispose();
            }
        }

        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            if (!isInit)
            {
                List<FieldInfo> fieldsList = GetFields();
                HK_FoldupAttribute foldupAttr = default;

                for (int i = 0; i < fieldsList.Count; i++)
                {
                    var foldup = Attribute.GetCustomAttribute(fieldsList[i], typeof(HK_FoldupAttribute)) as HK_FoldupAttribute;
                    CacheFoldProp cache;

                    if (foldup == null)
                    {
                        if (foldupAttr != null && foldupAttr.isFoldEverything)
                        {
                            if (!cacheFoldsDic.TryGetValue(foldupAttr.name, out cache))
                            {
                                cacheFoldsDic.Add(foldupAttr.name, new CacheFoldProp { attr = foldupAttr, types = new HashSet<string> { fieldsList[i].Name } });
                            }
                            else
                            {
                                cache.types.Add(fieldsList[i].Name);
                            }
                        }

                        continue;
                    }

                    foldupAttr = foldup;

                    if (!cacheFoldsDic.TryGetValue(foldup.name, out cache))
                    {
                        var expanded = EditorPrefs.GetBool(string.Format($"{foldup.name}{fieldsList[i].Name}{target.GetInstanceID()}"), false);
                        cacheFoldsDic.Add(foldup.name, new CacheFoldProp { attr = foldup, types = new HashSet<string> { fieldsList[i].Name }, isExpanded = expanded });
                    }
                    else
                    {
                        cache.types.Add(fieldsList[i].Name);
                    }
                }

                var property = serializedObject.GetIterator();
                var next = property.NextVisible(true);
                if (next)
                {
                    do
                    {
                        HandleFoldProp(property);
                    } while (property.NextVisible(false));
                }

                isInit = true;
            }

            if (propsList.Count == 0)
            {
                DrawDefaultInspector();
                return;
            }

            // 脚本的标题
            using (new EditorGUI.DisabledScope("m_Script" == propsList[0].propertyPath))
            {
                EditorGUILayout.Space();
                EditorGUILayout.PropertyField(propsList[0], true);
                EditorGUILayout.Space();
            }

            // 脚本的各类变量
            foreach (var pair in cacheFoldsDic)
            {
                EditorGUILayout.BeginVertical(new GUIStyle(GUI.skin.box));
                Foldout(pair.Value);
                EditorGUILayout.EndVertical();
                EditorGUI.indentLevel = 0;
            }

            EditorGUILayout.Space();

            for (int i = 1; i < propsList.Count; i++)
            {
                EditorGUILayout.PropertyField(propsList[i], true);
            }

            EditorGUILayout.Space();

            serializedObject.ApplyModifiedProperties();
        }

        List<FieldInfo> GetFields()
        {
            Dictionary<int, List<FieldInfo>> fields = new Dictionary<int, List<FieldInfo>>();
            List<FieldInfo> objectFields = new List<FieldInfo>();

            var t = target.GetType();
            var hash = t.GetHashCode();

            if (!fields.TryGetValue(hash, out objectFields))
            {
                var typeTree = new List<Type>();
                while (t.BaseType != null)
                {
                    typeTree.Add(t);
                    t = t.BaseType;
                }

                objectFields = target.GetType()
                        .GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic)
                        .OrderByDescending(x => typeTree.IndexOf(x.DeclaringType))
                        .ToList();
                fields.Add(hash, objectFields);
            }

            return objectFields;
        }

        void Foldout(CacheFoldProp cache)
        {
            cache.isExpanded = EditorGUILayout.Foldout(cache.isExpanded, cache.attr.name, true, new GUIStyle(EditorStyles.foldout));

            if (cache.isExpanded)
            {
                EditorGUI.indentLevel = 1;

                for (int i = 0; i < cache.props.Count; i++)
                {
                    EditorGUILayout.BeginVertical(new GUIStyle(GUI.skin.box));
                    EditorGUILayout.PropertyField(cache.props[i], new GUIContent(ObjectNames.NicifyVariableName(cache.props[i].name)), true);
                    EditorGUILayout.EndVertical();
                }
            }
        }

        void HandleFoldProp(SerializedProperty prop)
        {
            bool shouldBeFolded = false;

            foreach (var pair in cacheFoldsDic)
            {
                if (pair.Value.types.Contains(prop.name))
                {
                    var pr = prop.Copy();
                    shouldBeFolded = true;
                    pair.Value.props.Add(pr);

                    break;
                }
            }

            if (shouldBeFolded == false)
            {
                var pr = prop.Copy();
                propsList.Add(pr);
            }
        }

        class CacheFoldProp
        {
            public HashSet<string> types = new HashSet<string>();
            public List<SerializedProperty> props = new List<SerializedProperty>();
            public HK_FoldupAttribute attr;
            public bool isExpanded;

            public void Dispose()
            {
                props.Clear();
                types.Clear();
                attr = null;
            }
        }
    }
}